﻿using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;

namespace Athena.RouteAddress.Implementations
{
    using ExceptionHandler;
    public class DefaultRouteResolver : IRouteResolver
    {
        private readonly IRouteSelector _routeSelector;
        private readonly IServiceMultipleRouteManager _serviceMultipleRouteManager;
        private readonly ILogger<DefaultRouteResolver> _logger;

        private readonly ConcurrentDictionary<string, ServiceRoute> _serviceRouteDictionary;

        public DefaultRouteResolver(IRouteSelector routeSelector, IServiceMultipleRouteManager serviceMultipleRouteManager, ILogger<DefaultRouteResolver> logger)
        {
            _routeSelector = routeSelector;
            _serviceMultipleRouteManager = serviceMultipleRouteManager;

            _serviceMultipleRouteManager.Created += ServiceRouteCreated;
            _serviceMultipleRouteManager.Changed += ServiceRouteChanged;
            _serviceMultipleRouteManager.Removed += ServiceRouteRemoved;

            _logger = logger;
            _serviceRouteDictionary = new ConcurrentDictionary<string, ServiceRoute>();
        }

        public ConcurrentDictionary<string, ServiceRoute> ServiceRouteDictionary { get => _serviceRouteDictionary; }

        public async Task<Route> Resolver(string correlationId)
        {
            if (_serviceRouteDictionary.Count == 0)
            {
                await CreateServiceRouteDictionary();
            }

            if (!_serviceRouteDictionary.TryGetValue(correlationId, out ServiceRoute serviceRoute))
            {
                throw new RpcException(AthenaRpcInvokeResultCodeDisplay.RpcCorrelationIdException, $"服务Id:{correlationId},解析服务地址失败。");
            }
            return await _routeSelector.SelectAsync(serviceRoute);
        }

        public void Dispose()
        {
            _serviceRouteDictionary.Clear();
            _serviceMultipleRouteManager.Created -= ServiceRouteCreated;
            _serviceMultipleRouteManager.Changed -= ServiceRouteChanged;
            _serviceMultipleRouteManager.Removed -= ServiceRouteRemoved;
        }


        private void ServiceRouteCreated(object sender, ServiceMultipleRouteEventArgs e)
        {
            foreach (var currentServiceRoute in e.ServiceMultipleRoute.ServiceRoutes)
            {
                if (_serviceRouteDictionary.TryGetValue(currentServiceRoute.CorrelationId, out ServiceRoute serviceRoute))
                {
                    serviceRoute.Routes = serviceRoute.Routes.Concat(currentServiceRoute.Routes);
                }
                else
                {
                    _serviceRouteDictionary.TryAdd(currentServiceRoute.CorrelationId, currentServiceRoute);
                }
            }
        }

        private void ServiceRouteChanged(object sender, ServiceMultipleRouteEventArgs e)
        {
            var changedEventArgs = e as ServiceMultipleRouteChangedEventArgs;
            ServiceRouteRemoved(sender, new ServiceMultipleRouteEventArgs(changedEventArgs.RemoveServiceMultipleRoute));
            ServiceRouteCreated(sender, new ServiceMultipleRouteEventArgs(changedEventArgs.ServiceMultipleRoute));
        }

        private void ServiceRouteRemoved(object sender, ServiceMultipleRouteEventArgs e)
        {
            foreach (var currentServiceRoute in e.ServiceMultipleRoute.ServiceRoutes)
            {
                if (_serviceRouteDictionary.TryGetValue(currentServiceRoute.CorrelationId, out ServiceRoute serviceRoute))
                {
                    serviceRoute.Routes = serviceRoute.Routes.Except(currentServiceRoute.Routes);
                }
            }
        }


        private async Task CreateServiceRouteDictionary()
        {
            var serviceMultipleRoutes = await _serviceMultipleRouteManager.GetServiceMultipleRouteAsync();
            _serviceRouteDictionary.Clear();

            foreach (var serviceMultipleRoute in serviceMultipleRoutes)
            {
                foreach (var currentServiceRoute in serviceMultipleRoute.ServiceRoutes)
                {

                    if (_serviceRouteDictionary.TryGetValue(currentServiceRoute.CorrelationId, out ServiceRoute serviceRoute))
                    {
                        serviceRoute.Routes = serviceRoute.Routes.Concat(currentServiceRoute.Routes);
                    }
                    else
                    {
                        _serviceRouteDictionary.TryAdd(currentServiceRoute.CorrelationId, currentServiceRoute);
                    }
                }

            }
        }
    }
}
